package utils

import (
	"crypto/md5"
	"crypto/sha512"
	"encoding/hex"
	"fmt"
	"math"
	"math/big"
	rnd "math/rand"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/satori/go.uuid"
)

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const float64EqualityThreshold = 1e-9

func NewDeviceUUID() string {
	return uuid.NewV1().String()
}

func NewUUIDString() string {
	return uuid.NewV1().String()
}

func NewAppKey() string {
	return Md5str(uuid.NewV1().String())
}

func NewAuthTokenUUID() string {
	return Md5str(uuid.NewV1().String())
}

func TimeToString(t time.Time) string {
	return t.Format("2006-01-02 15:04:05")
}

func TimerPtrToString(t *time.Time) string {
	if t == nil {
		return ""
	}
	return TimeToString(*t)
}

func Md5str(src string) string {
	ctx := md5.New()
	ctx.Write([]byte(src))
	return hex.EncodeToString(ctx.Sum(nil))
}

func Md5byte(src []byte) string {
	ctx := md5.New()
	ctx.Write(src)
	return hex.EncodeToString(ctx.Sum(nil))
}

func CalcMd5(data []byte) string {
	ctx := md5.New()
	ctx.Write(data)
	return hex.EncodeToString(ctx.Sum(nil))
}

func Sha512Str(src string) string {
	h := sha512.New()
	h.Write([]byte(src))
	return hex.EncodeToString(h.Sum(nil))
}

func UnixTimestamp(t int64) time.Time {
	return time.Unix(t, 0)
}

//pow.. 计算x的n次方
func Pow(x, n int) int {
	ret := 1
	for n != 0 {
		if n%2 != 0 {
			ret = ret * x
		}
		n /= 2
		x = x * x
	}
	return ret
}

func ParseDate(t string) (time.Time, error) {
	layout := "2006-01-02"
	return time.Parse(layout, t)
}

func MonthBeginDate(year, month int) time.Time {
	v := fmt.Sprintf("%04d-%02d-%02d", year, month, 1)
	t, err := ParseDate(v)
	if err != nil {
		println("parse error???", err, v)
	}
	return t
}

func ParseTimestamp(t string) (time.Time, error) {
	layout := "2006-01-02 15:04:05"
	return time.Parse(layout, t)
}

func subTimeToString(t time.Duration) string {
	hour := getStrByTimeC(t.Hours())
	minutes_str := getStrByTimeC(t.Minutes())
	minutes_int, err := strconv.Atoi(minutes_str)
	if err != nil {
		err = fmt.Errorf("minutes %s 转换异常", minutes_str)
		return ""
	}
	minutes := minutes_int % 60
	seconds_str := getStrByTimeC(t.Seconds())
	seconds_int, err := strconv.Atoi(seconds_str)
	if err != nil {
		err = fmt.Errorf("seconds %s 转换异常", seconds_str)
		return ""
	}

	var seconds int
	if minutes_int == 0 {
		seconds = seconds_int
	} else {
		seconds = seconds_int % (minutes_int * 60)
	}
	return hour + "小时" + strconv.Itoa(minutes) + "分" + strconv.Itoa(seconds) + "秒"
}

func getStrByTimeC(f float64) string {
	f_str := strconv.FormatFloat(f, 'f', -1, 64)
	return strings.Split(f_str, ".")[0]
}

func Timetostr(t time.Time) string {
	return t.Format("2006-01-02 15:04:05")
}

func TimeDateParttostr(t time.Time) string {
	return t.Format("2006-01-02")
}

func RandStringBytes(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rnd.Intn(len(letterBytes))]
	}
	return string(b)
}

type DevicePath struct {
	Schema   string
	DeviceID uint
	Args     []string
}

// ParseDevicePath 解析语音控制平台传输的 ID
func ParseDevicePath(path string) (out *DevicePath, err error) {
	u, err := url.Parse(path)
	if err != nil {
		err = errors.Wrap(err, "url parse error.")
		return
	}

	out = &DevicePath{
		Schema: u.Scheme,
	}

	devID, err := strconv.Atoi(u.Host)
	if err != nil {
		err = errors.Wrap(err, "unknown devid.")
		return
	}
	out.DeviceID = uint(devID)
	out.Args = strings.Split(u.Path, "/")
	return
}

func apHash(str string) uint64 {
	hash := uint64(0xAAAAAAAA)

	for i := 0; i < len(str); i++ {
		if (i & 1) == 0 {
			hash ^= (hash << 7) ^ uint64(str[i])*(hash>>3)
		} else {
			hash ^= ^((hash << 11) + uint64(str[i]) ^ (hash >> 5))
		}
	}

	return hash
}

func VerifyEmailFormat(email string) bool {
	regular := `^[0-9a-z][_.0-9a-z-]{0,31}@([0-9a-z][0-9a-z-]{0,30}[0-9a-z]\.){1,4}[a-z]{2,4}$`
	reg := regexp.MustCompile(regular)
	return reg.MatchString(email)
}

func VerifyMobileFormat(mobileNum string) bool {
	regular := "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$"
	reg := regexp.MustCompile(regular)
	return reg.MatchString(mobileNum)
}

func VerifyUserNameFormat(username string) bool {
	// (字母开头，允许5-16字节，允许字母数字下划线)
	regular := "^[a-zA-Z][a-zA-Z0-9_]{4,15}$"
	reg := regexp.MustCompile(regular)
	return reg.MatchString(username)
}

func ChunksList(in []string, count int) (out [][]string) {
	baseLength := len(in) / count
	left := len(in) % count
	if left > 0 {
		baseLength += 1
	}
	for i := 0; i != baseLength; i++ {
		begin := i * count
		end := (i + 1) * count
		if end > len(in) {
			end = len(in)
		}
		cur := in[begin:end]
		out = append(out, cur)
	}

	return out
}

func RemoveDup(i []string) (out []string) {
	m := map[string]int{}
	for _, item := range i {
		m[item] += 1
	}
	for k, _ := range m {
		out = append(out, k)
	}

	return
}

func lbsRad(v float64) float64 {
	return v * math.Pi / 180.0
}

func GetDistance(lat1, lng1, lat2, lng2 float64) float64 {
	rLat1 := lbsRad(lat1)
	rLat2 := lbsRad(lat2)
	a := rLat1 - rLat2
	b := lbsRad(lng1) - lbsRad(lng2)
	s := 2 * math.Asin(math.Sqrt(math.Pow(math.Sin(a/2), 2)+
		math.Cos(rLat1)*math.Cos(rLat2)*math.Pow(math.Sin(b/2), 2)))
	return math.Round(s*6378.137*10000) / 10
}

func IsEqualFloat(a, b float64) bool {
	return math.Abs(a-b) <= float64EqualityThreshold
}

func FileExists(filename string) bool {
	info, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return false
	}
	return !info.IsDir()
}

func RandomString() string {
	r1 := rnd.Int63()
	b1 := big.NewInt(r1)
	return b1.Text(32)
}
